package engine.util.shader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Scanner;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL20;

public class ShaderLoader {
	private static final int MAX_INFO_LOG_LENGTH = 2048;
	private static final HashMap<String, Integer> cache = new HashMap<String, Integer>();
	private static final String SEPERATOR = "$$";
	
	public static int getShader(String vertexFile, String fragmentFile) {
		if(cache.containsKey(vertexFile + SEPERATOR + fragmentFile)) {
			return cache.get(vertexFile + SEPERATOR + fragmentFile);
		}
		
		if(!Shader.shadersAvailable()) {
			System.err.println("Shaders are not available on this system");
			return 0;
		}
		
		//Program erzeugen
		int program = ARBShaderObjects.glCreateProgramObjectARB();
		
		//Dateien einlesen
		if(vertexFile != null) {
			String vertexShaderContent = loadShaderFile(vertexFile);
			
			if(vertexShaderContent != null) {
				int vertexShader = ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);
				
				if(loadShader(vertexShader, vertexShaderContent, vertexFile)) {
					ARBShaderObjects.glAttachObjectARB(program, vertexShader);
				}
			}
		}
		
		if(fragmentFile != null) {
			String fragmentShaderContent = loadShaderFile(fragmentFile);
			
			if (fragmentShaderContent != null) {
				int fragmentShader = ARBShaderObjects
						.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
				if (loadShader(fragmentShader, fragmentShaderContent,
						fragmentFile)) {
					ARBShaderObjects.glAttachObjectARB(program, fragmentShader);
				}
			}
		}
		
		ARBShaderObjects.glLinkProgramARB(program);
		ARBShaderObjects.glValidateProgramARB(program);
		
		int success = GL20.glGetProgram(program, GL20.GL_LINK_STATUS);
		
		if(success == 0) {
			System.out.println("Error while link program with the shaders " + vertexFile + " and " + fragmentFile);
			return 0;
		}
		
		cache.put(vertexFile + SEPERATOR + fragmentFile, program);
		
		return program;
	}
	
	private static boolean loadShader(int shader, String shaderContent, String shaderName) {
		ARBShaderObjects.glShaderSourceARB(shader, shaderContent);
		ARBShaderObjects.glCompileShaderARB(shader);
		
		int success = GL20.glGetShader(shader, GL20.GL_COMPILE_STATUS);
		
		if(success == 0) {
			ByteBuffer infoLog = BufferUtils.createByteBuffer(MAX_INFO_LOG_LENGTH);
			GL20.glGetShaderInfoLog(shader, null, infoLog);
			
			infoLog.rewind();
			
			System.out.println("Error occured while loading: " + shaderName);
			
			char log = 0;
			while((log = (char)infoLog.get()) != 0) {
				System.out.print((char)log);
			}
			
			System.out.println();
			infoLog.clear();
			
			return false;
		}
		
		return true;
	}
	
	private static String loadShaderFile(String file) {
		StringBuffer buffer = new StringBuffer();
		
		File f = new File(file);
		
		InputStream inputStream = ShaderLoader.class.getClassLoader().getResourceAsStream(file);
		
		try{
			Scanner scanner = null;
			
			if(f.exists()) {
				scanner = new Scanner(new FileInputStream(file));
			} else {
				scanner = new Scanner(inputStream);
			}
			
			while(scanner.hasNext()) {
				String s = scanner.nextLine() + "\n";
				buffer.append(s);
			}
			
			scanner.close();
			
			return buffer.toString();
		} catch(NullPointerException ex) {
			System.err.println("Could not find: " + file);
		} catch (FileNotFoundException e) {
			System.err.println("Could not find: " + file);
		}
		
		return null;
	}
}
